﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Xml.Serialization;

namespace GianosNET.Common.Classes
{
    public class ServerInfo
    {
        // Anagrafica dei servizi abilitati
        private Dictionary<Guid, RemotedClass> obj = new Dictionary<Guid, RemotedClass>();

        // Utenti/client autenticati
        private readonly AuthenticatedUserCollection users = new AuthenticatedUserCollection();

        // log dei servizi (il log per gli errori ha come chiave lo stesso guid del servizio)
        private readonly Dictionary<Guid, LogQueue> logs = new Dictionary<Guid, LogQueue>();

        public string ServerVersion { get; set; }

        #region Eventi da agganciare alla classe RemotedServiceManager e da propagare ai client

        public event CommonHandlers.LogChangeEventHandler OnLogChanged;
        public event CommonHandlers.ServiceStatusChangedEventHandler OnServiceStatusChanged;
        public event CommonHandlers.LoopingStatusChangedEventHandler OnLoopingStatusChanged;
        public event CommonHandlers.IdleCountdownChangedEventHandler OnIdleCountdownChanged;
        public event CommonHandlers.ForceFullClientRefresh OnForceFullClientRefresh;

        #endregion

        #region Registrazione dei servizi

        public void RegisterService(RemotedClass c)
        {
            if (obj.ContainsValue(c))
                throw new Exception("Il servizio è già registrato");
            
            obj.Add(c.UniqueIdentifier, c);
            
            // Crea il log delle exception
            logs.Add(c.UniqueIdentifier, new LogQueue(500));
        }

        #endregion

        #region Comandi sui servizi

        public void ServiceStart(Guid uid)
        {
            if (obj.ContainsKey(uid))
            {
                if (obj[uid].Status != RemotedClass.RemotedClassStatusEnum.Started)
                    obj[uid].Start();
            }
            else
                throw new Exception("Servizio non trovato");
        }

         public void StartLoopingSession(Guid uid)
         {
             if (obj.ContainsKey(uid))
             {
                 if (obj[uid].Status == RemotedClass.RemotedClassStatusEnum.Started)
                     if (obj[uid] is LoopingService)
                     {
                         LoopingService svc = (LoopingService) obj[uid];
                         if (svc.LoopingState == LoopingService.LoopingStateEnum.Idle)
                             svc.ForceRestart();
                     }
             }
             else
                 throw new Exception("Servizio non trovato");
         }

        public void SetLoopingPause(Guid uid, int secs)
        {
            if (obj.ContainsKey(uid))
            {
                if (obj[uid].Status == RemotedClass.RemotedClassStatusEnum.Started)
                    if (obj[uid] is LoopingService)
                    {
                        LoopingService svc = (LoopingService)obj[uid];
                        svc.LoopingPause = secs;
                    }
            }
            else
                throw new Exception("Servizio non trovato");
        }

        public void ServiceStop(Guid uid)
        {
            if (obj.ContainsKey(uid))
            {
                if (obj[uid].Status != RemotedClass.RemotedClassStatusEnum.Stopped)
                    obj[uid].Stop();
            }
            else
                throw new Exception("Servizio non trovato");
        }

        #endregion

        #region Logs

        public Guid RegisterLog(int maxLength)
        {
            Guid g = Guid.NewGuid();
            logs.Add(g, new LogQueue(maxLength));
            return g;
        }

        public void EnqueueExceptionInLog(RemotedClass sender, Exception ex)
        {
            EnqueueInLog(sender.UniqueIdentifier, ex.Message, true);
            if (ex.InnerException != null)
                EnqueueInLog(sender.UniqueIdentifier, ex.InnerException.Message, false);
        }

        public void EnqueueInLog(Guid g, Type t, object o)
        {
            EnqueueInLog(g, t, o, false);
        }
        public void EnqueueInLog(Guid g, Type t, object o, bool includeTimestamp)
        {
            XmlSerializer x = new XmlSerializer(t);
            StringWriter sw = new StringWriter();
            x.Serialize(sw, o);
            string serializedXml = sw.ToString();

            EnqueueInLog(g, serializedXml, includeTimestamp);
        }

        public void EnqueueInLog(Guid g, string msg)
        {
            EnqueueInLog(g, msg, false);
        }
        public void EnqueueInLog(Guid g, string msg, bool includeTimestamp)
        {
            if (logs.ContainsKey(g))
            {
                if (includeTimestamp)
                    msg = string.Format("[{0}]: {1}", DateTime.Now.ToString("u"), msg);

                logs[g].Enqueue(msg);
                if (OnLogChanged != null)
                    OnLogChanged(g, msg);
            }
            else
                throw new Exception("Il log non esiste");
        }

        #endregion

        #region Cambi di stato

        public void PublishServiceStatusChange(Guid svcId, RemotedClass.RemotedClassStatusEnum status)
        {
            if (OnServiceStatusChanged != null)
                OnServiceStatusChanged(svcId, status);
        }

        public void PublishLoopingStatusChange(Guid svcId, LoopingService.LoopingStateEnum status)
        {
            if (OnLoopingStatusChanged != null)
                OnLoopingStatusChanged(svcId, status);
        }

        public void PublishIdleCountdown(Guid svcId, int secsBeforeNextExecution)
        {
            if (OnIdleCountdownChanged != null)
                OnIdleCountdownChanged(svcId, secsBeforeNextExecution);
        }

        #endregion

        #region Logged users

        public void RegisterUser(AuthenticatedUser u)
        {
            users.Add(u.Token, u);
        }

        private void LogOffUser(Guid uid)
        {
            if (users.ContainsKey(uid))
                users.Remove(uid);
        }

        #endregion

        public ServiceInfo[] Services()
        {
            ServiceInfo[] res = new ServiceInfo[obj.Count];

            int i = 0;
            foreach (KeyValuePair<Guid, RemotedClass> pair in obj)
            {
                res[i] = (new ServiceInfo(pair.Key, pair.Value.ServiceName, pair.Value.VersionNumber, pair.Value.InstanceName,
                                          pair.Value.GetType().FullName, 
                                          pair.Value.Gestore,
                                     	  pair.Value.MainLogId,
                                          pair.Value.logQueueId, 
										  pair.Value.Status,
                                          (pair.Value is LoopingService)
                                              ? ((LoopingService) pair.Value).LoopingState
                                              : LoopingService.LoopingStateEnum.Stopped,
                                          (pair.Value is LoopingService)
                                              ? ((LoopingService) pair.Value).LoopingPause
                                              : 0
											  ));
                i++;
            }

            return res;
        }

        public LogQueue GetLog(Guid g)
        {
            if (logs.ContainsKey(g))
                return logs[g];

            return null;
        }

        public int UnregisterServices(string configname)
        {
            List<KeyValuePair<Guid, RemotedClass>> tmp = new List<KeyValuePair<Guid, RemotedClass>>();

            int res = 0;
            foreach (KeyValuePair<Guid, RemotedClass> pair in obj)
                if (pair.Value is LoopingService)
                {
                    Type t = pair.Value.GetType();
                    PropertyInfo p = t.GetProperty("DefaultConfigArea",
                        BindingFlags.NonPublic | BindingFlags.Static);
                    if (p != null && p.GetValue(null, null) as string == configname)
                        tmp.Add(pair);
                }

            // starts signalling services to stop
            while (tmp.Count > 0)
            {
                KeyValuePair<Guid, RemotedClass> pair = tmp[0];
                ServiceStop(pair.Key);
                while (pair.Value.Job.IsBusy)
                    Thread.Sleep(500);
                tmp.Remove(pair);
                obj.Remove(pair.Key);
                res++;
            }

            return res;
        }

        public void ForceClientsRefresh()
        {
            if (OnForceFullClientRefresh != null)
                OnForceFullClientRefresh();
        }
    }
}
